package com.aceitunaproject.server.combat;

import java.util.logging.Logger;
import com.aceitunaproject.server.AceitunaContext;
import com.aceitunaproject.server.ServerConfiguration;
import com.aceitunaproject.server.entity.unit.Unit;
import com.aceitunaproject.server.equipment.Equipment;
import com.aceitunaproject.server.map.MapManager;
import com.aceitunaproject.server.map.MapTile;
import com.aceitunaproject.server.terrain.TerrainType;
import com.aceitunaproject.shared.exceptions.gamerules.EnumIllegalAction;
import com.aceitunaproject.shared.exceptions.gamerules.IllegalActionException;

/**
 * @author Federico Bergstein
 * 
 */
public class CombatSystemImpl implements CombatSystem {

	private static final Logger logger = Logger
	.getLogger(CombatSystemImpl.class.getName());
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.aceitunaproject.server.combat.CombatSystem#fight(com.aceitunaproject
	 * .server.entity.unit.Unit, com.aceitunaproject.server.entity.unit.Unit)
	 */
	@Override
	public CombatResult fight(Unit attacker, Unit defender) {

		int basicAttackerLethality = 1;
		int basicAttackerDefense = 0;
		int basicDefenderLethality = 1;
		int basicDefenderDefense = 0;

		int attackerLethalityModifiers = 0;
		int attackerDefenseModifiers = 0;
		int defenderLethalityModifiers = 0;
		int defenderDefenseModifiers = 0;

		MapTile tile = AceitunaContext.getMapManager().getTile(
				defender.getCurrentPosition());

		// basic
		for (Equipment e : attacker.getEquipment().values()) {
			CombatEffects ce = e.getBasicCombatEffects();
			basicAttackerLethality += ce.getAttackerLethalityBonus();
			basicAttackerDefense += ce.getAttackerDefenseBonus();
		}

		for (Equipment e : defender.getEquipment().values()) {
			CombatEffects ce = e.getBasicCombatEffects();
			basicDefenderLethality += ce.getDefenderLethalityBonus();
			basicDefenderDefense += ce.getDefenderDefenseBonus();
		}

		{// Calculates attacker modifiers

			for (Equipment e : attacker.getEquipment().values()) {
				for (TerrainType t : tile.getTerrainTypes().values()) {
					CombatEffects ce = e.getCombatEffects(t);
					attackerLethalityModifiers += ce
							.getAttackerLethalityBonus();
					attackerDefenseModifiers += ce.getAttackerDefenseBonus();
				}
			}

			for (Equipment e : attacker.getEquipment().values()) {
				for (Equipment d : defender.getEquipment().values()) {
					CombatEffects ce = e.getCombatEffects(d);
					attackerLethalityModifiers += ce
							.getAttackerLethalityBonus();
					attackerDefenseModifiers += ce.getAttackerDefenseBonus();
					for (TerrainType t : tile.getTerrainTypes().values()) {
						CombatEffects cce = e.getCombatEffects(t, d);
						attackerLethalityModifiers += cce
								.getAttackerLethalityBonus();
						attackerDefenseModifiers += cce
								.getAttackerDefenseBonus();
					}

				}
			}

		}
		// Calculates defender modifiers

		for (Equipment e : defender.getEquipment().values()) {
			for (TerrainType t : tile.getTerrainTypes().values()) {
				CombatEffects ce = e.getCombatEffects(t);
				defenderLethalityModifiers += ce.getDefenderLethalityBonus();
				defenderDefenseModifiers += ce.getDefenderDefenseBonus();
			}
		}
		for (Equipment e : defender.getEquipment().values()) {
			for (Equipment d : attacker.getEquipment().values()) {
				CombatEffects ce = e.getCombatEffects(d);
				defenderLethalityModifiers += ce.getDefenderLethalityBonus();
				defenderDefenseModifiers += ce.getDefenderDefenseBonus();

				for (TerrainType t : tile.getTerrainTypes().values()) {
					CombatEffects cee = e.getCombatEffects(t, d);
					defenderLethalityModifiers += cee
							.getDefenderLethalityBonus();
					defenderDefenseModifiers += cee.getDefenderDefenseBonus();
				}

			}
		}

		double attackerLethality = (basicAttackerLethality * getLethalityModifier())
				+ (attackerLethalityModifiers * getLethalityModifier());

		double defenderLethality = (basicDefenderLethality * getLethalityModifier())
				+ (defenderLethalityModifiers * getLethalityModifier());

		double attackerDefense = (basicAttackerDefense * getLethalityModifier())
				+ (attackerDefenseModifiers * getLethalityModifier());

		double defenderDefense = (basicDefenderDefense * getLethalityModifier())
				+ (defenderDefenseModifiers * getLethalityModifier());

		double attackerMoraleModifier = getMoraleModifier(attacker);
		double defenderMoraleModifier = getMoraleModifier(defender);

		defenderLethality *= defenderMoraleModifier;
		defenderDefense *= defenderMoraleModifier;

		attackerLethality *= attackerMoraleModifier;
		attackerDefense *= attackerMoraleModifier;

		long attackerCasualties = Math
				.round((defenderLethality - attackerDefense)
						* defender.getPersons());
		
		if(attackerCasualties < 0)
			attackerCasualties = 0;
		
		long defenderCasualties = Math
				.round((attackerLethality - defenderDefense)
						* attacker.getPersons());
		
		if(defenderCasualties < 0)
			defenderCasualties = 0;

		// applies random effects 10% more or less casualties
		long attackercasualtiesvariation = (10 * attackerCasualties) / 100;
		long defendercasualtiesvariation = (10 * defenderCasualties) / 100;

		attackerCasualties = Math
				.round((attackerCasualties - attackercasualtiesvariation)
						+ ((attackercasualtiesvariation * 2) * Math.random()));
		
		defenderCasualties = Math
				.round((defenderCasualties - defendercasualtiesvariation)
						+ ((defendercasualtiesvariation * 2) * Math.random()));

		// TODO: check morale to see if defending unit retreats.
		// TODO: check morale to see if attacking unit retreats (in which case
		// it will not take the tile, even if the deffending unit also retreats)

		if (attacker.getPersons() > attackerCasualties) {
			attacker.setPersons(attacker.getPersons() - attackerCasualties);

		} else {
			attacker.destroy();
		}

		if (defender.getPersons() > defenderCasualties) {
			defender.setPersons(defender.getPersons() - defenderCasualties);
		} else {

			defender.destroy();
		}
		logger.warning("Attacker casualties:" + attackerCasualties);
		logger.warning("Defender casualties:" + defenderCasualties);
		return new CombatResultImpl(attackerCasualties, defenderCasualties);
	}

	private double getMoraleModifier(Unit unit) {
		return unit.getMorale() / 100;
	}

	private double getLethalityModifier() {

		return 0.3;
	}

	@Override
	public void manageCombat(Unit attackingUnit, long enemyUnitId)
			throws IllegalActionException {

		// TODO: Ranged attack unit (ie: archers,catapults,cannon) cases are not
		// being considered!

		// checks per use case:
		// That the unit to attack is in the same Map Tile than the attacking
		// unit or in an adjacent Map Tile.
		
		MapManager mapMgr = AceitunaContext.getMapManager();
		Unit enemyUnit = AceitunaContext.getUnitManager().getUnit(enemyUnitId);
		
		MapTile unitTile = mapMgr.getTile(attackingUnit.getCurrentPosition());
		MapTile enemyTile = mapMgr.getTile(enemyUnit.getCurrentPosition());
		if (!(unitTile.equals(enemyTile) || mapMgr.getAdjacentTiles(unitTile)
				.contains(enemyTile))) {
			throw new IllegalActionException(
					EnumIllegalAction.ATTACK_UNIT_UNIT_OUT_OF_RANGE);
		}
		// TODO:That the unit's equipment allows it to enter in the Map Tile of
		// the unit to attack.
		if (!enemyTile.unitCanOccupyTile(attackingUnit)) {
			throw new IllegalActionException(
					EnumIllegalAction.ATTACK_UNIT_UNIT_CANNOT_OCCUPY_ENEMY_TILE);
		}
		// TODO:That the unit has the necessary Time Units to attack (attack
		// cost + terrain cost of the enemy unit's map tile(unless they are in
		// the same Map Tile)).
		if (enemyTile.getMovementCost(attackingUnit)
				+ ServerConfiguration.getAttackingTimeUnitCost() > attackingUnit
				.getTimeUnits()) {
			
			throw new IllegalActionException(EnumIllegalAction.ATTACK_UNIT_OUT_OF_TIME);

		}

		

		fight(attackingUnit, enemyUnit);
		

	}

}
